﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using AMS.DataAccess.Common;
using AMS.DataAccess.Properties;

namespace AMS.DataAccess.Sql
{
    /// <summary>
    /// 构造UPDATE的SQL语句类。
    /// </summary>
    public class SqlUpdate : SqlStatement, IConditionProvider<SqlUpdate>
    {
        #region 构造函数
        /// <summary>
        /// 构造UPDATE的SQL语句类
        /// </summary>
        /// <param name="tableName">表名</param>
        public SqlUpdate(string tableName)
            : this(tableName, false)
        {
        }

        /// <summary>
        /// 构造UPDATE的SQL语句类
        /// </summary>
        /// <param name="tableName">表名</param>
        /// <param name="compactMode">是否紧凑模式</param>
        public SqlUpdate(string tableName, bool compactMode)
        {
            if (string.IsNullOrEmpty(tableName))
            {
                throw new ArgumentNullException(Resources.TableNameRequired);
            }

            this.tableName = tableName;
            conditionBuilder = new ConditionBuilder();
            CompactMode = compactMode;

            sbSql = new StringBuilder();
            versionInfo = new VersionInfo();

            isFirstColumn = true;
        }
        #endregion

        #region Fields & Properties
        /// <summary>
        /// 构造的SQL语句
        /// </summary>
        private StringBuilder sbSql;

        /// <summary>
        /// 条件表达式
        /// </summary>
        private StringBuilder Condition
        {
            get { return conditionBuilder.Condition; }
        }

        /// <summary>
        /// 表名
        /// </summary>
        private string tableName;

        /// <summary>
        /// 表示是否添加的第一个列
        /// </summary>
        private bool isFirstColumn;

        /// <summary>
        /// 版本号列信息
        /// </summary>
        private VersionInfo versionInfo;

        /// <summary>
        /// 条件表达式构造器
        /// </summary>
        ConditionBuilder conditionBuilder;

        /// <summary>
        /// 获取或设置一个值，表示是否紧凑模式。如果是紧凑模式，则尽量将SQL语句构造在一行以内。
        /// </summary>
        public override bool CompactMode
        {
            get { return base.CompactMode; }
            set
            {
                base.CompactMode = value;
                conditionBuilder.CompactMode = value;
            }
        }
        #endregion

        #region AddObjectColumn
        /// <summary>
        /// 添加一个object属性的列
        /// </summary>
        /// <param name="column">列名</param>
        /// <param name="value">列的值</param>
        /// <param name="property">属性</param>
        /// <param name="attribute">属性对应的标注</param>
        private void AddObjectColumn(string column, object value, PropertyInfo property, DBColumnAttribute attribute)
        {
            // please deal with NULL value
            if (value == null)
            {
                AddColumnNullValue(column);
                return;
            }

            Type propertyType = property.PropertyType;
            if (propertyType.Equals(typeof(string)))
            {
                AddColumn(column, AdoConverter.ToString(value));
            }
            else if (propertyType.Equals(typeof(bool)))
            {
                AddColumn(column, AdoConverter.ToBoolean(value));
            }
            else if (propertyType.Equals(typeof(bool?)))
            {
                if (value == null)
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, AdoConverter.ToBoolean(value));
                }
            }
            else if (propertyType.Equals(typeof(int)) || propertyType.IsSubclassOf(typeof(Enum)))
            {
                var ivalue = AdoConverter.ToInt32(value);
                if (attribute.NullValue != null && (ivalue == AdoConverter.ToInt32(attribute.NullValue)))
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, ivalue);
                }
            }
            else if (propertyType.Equals(typeof(int?)))
            {
                if (value == null)
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, AdoConverter.ToInt32(value));
                }
            }
            else if (propertyType.Equals(typeof(long)))
            {
                var ivalue = AdoConverter.ToInt64(value);
                if (attribute.NullValue != null && (ivalue == AdoConverter.ToInt64(attribute.NullValue)))
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, ivalue);
                }
            }
            else if (propertyType.Equals(typeof(long?)))
            {
                if (value == null)
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, AdoConverter.ToInt64(value));
                }
            }
            else if (propertyType.Equals(typeof(Guid)))
            {
                var ivalue = AdoConverter.ToGuid(value);
                if (attribute.NullValue != null)
                {
                    if (!ivalue.ToString().Equals(attribute.NullValue.ToString(), StringComparison.InvariantCultureIgnoreCase))
                    {
                        AddColumn(column, ivalue);
                    }
                    else
                    {
                        AddColumnNullValue(column);
                    }
                }
                else
                {
                    if (ivalue == Guid.Empty)
                    {
                        AddColumnNullValue(column);
                    }
                    else
                    {
                        AddColumn(column, ivalue);
                    }
                }
            }
            else if (propertyType.Equals(typeof(short)))
            {
                var ivalue = AdoConverter.ToShort(value);
                if (attribute.NullValue != null && (ivalue == AdoConverter.ToShort(attribute.NullValue)))
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, ivalue);
                }
            }
            else if (propertyType.Equals(typeof(short?)))
            {
                if (value == null)
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, AdoConverter.ToShort(value));
                }
            }
            else if (propertyType.Equals(typeof(decimal)))
            {
                var dvalue = AdoConverter.ToDecimal(value);
                if (attribute.NullValue != null && (dvalue == AdoConverter.ToDecimal(attribute.NullValue)))
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, AdoConverter.ToDecimal(value));
                }
            }
            else if (propertyType.Equals(typeof(decimal?)))
            {
                if (value == null)
                {
                    AddColumnNullValue(column);
                }
                else
                {
                    AddColumn(column, AdoConverter.ToDecimal(value));
                }
            }
            else if (propertyType.Equals(typeof(DateTime)))
            {
                if (attribute is DBDateTimeAttribute)
                {
                    var ivalue = AdoConverter.ToDateTime(value);
                    if (attribute.NullValue != null && (ivalue == AdoConverter.ToDateTime(attribute.NullValue)))
                    {
                        AddColumnNullValue(column);
                    }
                    else
                    {
                        if (ivalue == DateTime.MinValue)
                        {
                            AddColumnNullValue(column);
                        }
                        else
                        {
                            DateTimeType dtType = ((DBDateTimeAttribute)attribute).DateTimeType;
                            AddColumn(column, ivalue, dtType);
                        }
                    }
                }
                else
                {
                    throw new AttributeException(string.Format(Resources.DBDateTimeAttributeRequiredFormat, property.Name));
                }
            }
            else if (propertyType.Equals(typeof(DateTime?)))
            {
                if (attribute is DBDateTimeAttribute)
                {
                    if (value == null)
                    {
                        AddColumnNullValue(column);
                    }
                    else
                    {
                        var ivalue = AdoConverter.ToDateTime(value);
                        if (attribute.NullValue != null && (ivalue == AdoConverter.ToDateTime(attribute.NullValue)))
                        {
                            AddColumnNullValue(column);
                        }
                        else
                        {
                            if (ivalue == DateTime.MinValue)
                            {
                                AddColumnNullValue(column);
                            }
                            else
                            {
                                DateTimeType dtType = ((DBDateTimeAttribute)attribute).DateTimeType;
                                AddColumn(column, ivalue, dtType);
                            }
                        }
                    }
                }
                else
                {
                    throw new AttributeException(string.Format(Resources.DBDateTimeAttributeRequiredFormat, property.Name));
                }
            }
            else
            {
                throw new NotSupportedException(string.Format(Resources.DataTypeNotSupportedFormat, column));
            }
        }

        /// <summary>
        /// 准备添加UPDATE的内容
        /// </summary>
        /// <param name="columnName">列名</param>
        private void Prepare(string columnName)
        {
            if (isFirstColumn)
            {
                isFirstColumn = false;
                sbSql.Append(CompactMode ? " " : "    ");
            }
            else
            {
                sbSql.Append(CompactMode ? ", " : "    ,");
            }

            sbSql.Append(columnName);
            sbSql.Append(" = ");
        }
        #endregion

        #region Add Business Object
        /// <summary>
        /// 添加一个BusinessObject
        /// </summary>
        /// <param name="businessObject">需要非NULL</param>
        public void AddBusinessObject(object businessObject)
        {
            AddBusinessObject(businessObject, businessObject.GetType(), false);
        }

        /// <summary>
        /// 添加一个BusinessObject
        /// </summary>
        /// <param name="businessObject">需要非NULL</param>
        /// <param name="setPrimaryKeyFieldAsCondition">是否解析主键列解析成 update 的 where 条件</param>
        public void AddBusinessObject(object businessObject, bool setPrimaryKeyFieldAsCondition)
        {
            AddBusinessObject(businessObject, businessObject.GetType(), setPrimaryKeyFieldAsCondition);
        }

        /// <summary>
        /// 添加一个BusinessObject，并使用相应的类型。
        /// </summary>
        /// <param name="businessObject">需要非NULL</param>
        /// <param name="objectType">BO的类型，不一是BO本身的类型，但必需是BO所继承的类型</param>
        public void AddBusinessObject(object businessObject, Type objectType)
        {
            AddBusinessObject(businessObject, objectType, false);
        }

        /// <summary>
        /// 添加一个BusinessObject，并使用相应的类型。
        /// </summary>
        /// <param name="businessObject">需要非NULL</param>
        /// <param name="objectType">BO的类型，不一是BO本身的类型，但必需是BO所继承的类型</param>
        /// <param name="setPrimaryKeyFieldAsCondition">是否解析主键列解析成 update 的 where 条件</param>
        public void AddBusinessObject(object businessObject, Type objectType, bool setPrimaryKeyFieldAsCondition)
        {
            if (objectType.IsInterface)
            {
                throw new ArgumentException(Resources.InterfaceTypeNotSupport);
            }

            if (businessObject.GetType() != objectType && !businessObject.GetType().IsSubclassOf(objectType))
            {
                throw new ArgumentException(Resources.TypeOfBOCanOnlyBeTheGivenType);
            }

            ObjectInfo objectInfo = ObjectUtils.GetBOObjectInfo(objectType);
            foreach (string key in objectInfo.DBFields.Keys)
            {
                PropertyInfo property = objectInfo.DBFields[key];
                if (objectInfo.PKFields.Contains(property))
                {
                    // 对于主键的字段，不包括在Update语句的set表达式中
                    continue;
                }

                object[] attributes = property.GetCustomAttributes(true);
                foreach (Attribute attr in attributes)
                {
                    if (attr is DBColumnAttribute)
                    {
                        DBColumnAttribute dbcolumnAttr = attr as DBColumnAttribute;
                        if (dbcolumnAttr is DBVersionColumnAttribute)
                        {
                            AddVersionColumn(businessObject, property, (DBVersionColumnAttribute)dbcolumnAttr);
                            continue;
                        }

                        if (!((DBColumnAttribute)attr).IgnoreUpdate)
                        {
                            object retValue = ObjectUtils.GetPropertyValue(businessObject, property);
                            AddObjectColumn(key, retValue, property, (DBColumnAttribute)attr);
                        }

                        break;
                    }
                }
            }

            if (versionInfo.HasVersionInfo)
            {
                AddVersionColumnInternal();
            }

            if (setPrimaryKeyFieldAsCondition)
            {
                AddPrimaryKeyFieldAsCondition(businessObject, objectType);
            }
        }

        #endregion

        #region 处理数据列
        /// <summary>
        /// 添加一个版本号列
        /// </summary>
        /// <param name="businessObject"></param>
        /// <param name="property"></param>
        /// <param name="dbcolumnAttr"></param>
        private void AddVersionColumn(object businessObject, PropertyInfo property, DBVersionColumnAttribute dbcolumnAttr)
        {
            if (property.PropertyType == typeof(int))
            {
                if (!string.IsNullOrEmpty(versionInfo.VersionColumn))
                {
                    throw new AttributeException(Resources.OnlyOneVersionNumberColumnSupported);
                }

                versionInfo.VersionColumn = dbcolumnAttr.Column;
                object retValue = ObjectUtils.GetPropertyValue(businessObject, property);
                versionInfo.CurrentVersion = AdoConverter.ToInt32(retValue);
            }
            else if (property.PropertyType == typeof(DateTime))
            {
                if (!string.IsNullOrEmpty(versionInfo.VersionDateColumn))
                {
                    throw new AttributeException(Resources.OnlyOneVersionDateColumnSupported);
                }

                versionInfo.VersionDateColumn = dbcolumnAttr.Column;
            }
            else
            {
                throw new AttributeException(Resources.VersionDataTypeLimit);
            }
        }

        /// <summary>
        /// 添加一个版本号列
        /// </summary>
        private void AddVersionColumnInternal()
        {
            if (versionInfo.HasVersionInfo)
            {
                AddColumn(versionInfo.VersionColumn + " = " + versionInfo.VersionColumn + " + 1");
                if (!string.IsNullOrEmpty(versionInfo.VersionDateColumn))
                {
                    AddColumn(versionInfo.VersionDateColumn, DateTime.Now, DateTimeType.DateTime);
                }
            }
        }

        /// <summary>
        /// 添加一个 update 列
        /// </summary>
        /// <param name="expression">添加的表达式</param>
        public SqlUpdate AddColumn(SqlExpression expression)
        {
            return AddColumn(expression.ToString());
        }

        /// <summary>
        /// 添加一个 update 列
        /// </summary>
        /// <param name="expression">添加的表达式</param>
        public SqlUpdate AddColumn(string expression)
        {
            if (isFirstColumn)
            {
                isFirstColumn = false;
                sbSql.Append(CompactMode ? " " : "    ");
            }
            else
            {
                sbSql.Append(CompactMode ? ", " : "    ,");
            }

            NL(sbSql.Append(expression));
            return this;
        }

        /// <summary>
        /// 添加一个 update 列，并设置列的值为 NULL
        /// </summary>
        /// <param name="columnName">列名</param>
        public SqlUpdate AddColumnNullValue(string columnName)
        {
            Prepare(columnName);
            NL(sbSql.Append("NULL"));
            return this;
        }

        /// <summary>
        /// 添加一个 update 列，用 A = B 将列名和值组合起来
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">值</param>
        public SqlUpdate AddColumn(string columnName, string value)
        {
            Prepare(columnName);
            string quotedValue = SqlStringUtils.QuotedString(value);
            if (quotedValue == null)
            {
                quotedValue = "NULL";
            }
            else if (quotedValue == string.Empty)
            {
                // NOTE: treate empty as null
                quotedValue = KeepEmptyString ? "''" : "NULL";
            }

            NL(sbSql.Append(quotedValue));
            return this;
        }

        private SqlUpdate AddNumberColumnInternal<T>(string columnName, T value)
        {
            Prepare(columnName);
            NL(sbSql.Append(value.ToString()));
            return this;
        }

        /// <summary>
        /// 添加一个 update 列，用 A = B 将列名和值组合起来
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">值</param>
        public SqlUpdate AddColumn(string columnName, int value)
        {
            return AddNumberColumnInternal<int>(columnName, value);
        }

        /// <summary>
        /// 添加一个 update 列，用 A = B 将列名和值组合起来
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">值</param>
        public SqlUpdate AddColumn(string columnName, short value)
        {
            return AddNumberColumnInternal<short>(columnName, value);
        }

        /// <summary>
        /// 添加一个 update 列，用 A = B 将列名和值组合起来
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">值</param>
        public SqlUpdate AddColumn(string columnName, long value)
        {
            return AddNumberColumnInternal<long>(columnName, value);
        }

        /// <summary>
        /// 添加一个 update 列，用 A = B 将列名和值组合起来
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">值</param>
        public SqlUpdate AddColumn(string columnName, decimal value)
        {
            return AddNumberColumnInternal<decimal>(columnName, value);
        }

        /// <summary>
        /// 添加一个 update 列，用 A = B 将列名和值组合起来
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">值</param>
        public SqlUpdate AddColumn(string columnName, bool value)
        {
            return AddNumberColumnInternal<int>(columnName, value ? 1 : 0);
        }

        /// <summary>
        /// 添加一个 update 列，用 A = B 将列名和值组合起来
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">值</param>
        public SqlUpdate AddColumn(string columnName, Guid value)
        {
            Prepare(columnName);
            sbSql.Append("'");
            sbSql.Append(value);
            NL(sbSql.Append("'"));
            return this;
        }

        /// <summary>
        /// 添加一个 update 的 DateTime 类型列，用 A = B 将列名和值组合起来
        /// </summary>
        /// <param name="columnName">列名</param>
        /// <param name="value">值</param>
        /// <param name="kind">时间日期类型</param>
        public SqlUpdate AddColumn(string columnName, DateTime value, DateTimeType kind)
        {
            if (value == DateTime.MinValue || value == DateTime.MaxValue)
            {
                return this;
            }

            Prepare(columnName);
            string strValue = SqlStringUtils.DateTimeToString(value, kind);
            sbSql.Append("'");
            sbSql.Append(strValue);
            NL(sbSql.Append("'"));
            return this;
        }
        #endregion

        #region 处理条件
        /// <summary>
        /// 将对象的主键添加为 where 条件
        /// </summary>
        /// <param name="businessObject">业务对象</param>
        public void AddPrimaryKeyFieldAsCondition(object businessObject)
        {
            conditionBuilder.AddPrimaryKeyFieldAsCondition(businessObject);
        }

        /// <summary>
        /// 将对象的主键添加为 where 条件
        /// </summary>
        /// <param name="businessObject">业务对象</param>
        /// <param name="objectType">对象的实际类型</param>
        public void AddPrimaryKeyFieldAsCondition(object businessObject, Type objectType)
        {
            conditionBuilder.AddPrimaryKeyFieldAsCondition(businessObject, objectType);
        }

        /// <summary>
        /// 添加一个条件
        /// </summary>
        /// <param name="expression">条件表达式，如果已经有条件表达式，则用 AND 将它们连接起来</param>
        public SqlUpdate AddCondition(string expression)
        {
            conditionBuilder.AddCondition(expression);
            return this;
        }

        /// <summary>
        /// 添加一个条件
        /// </summary>
        /// <param name="condition">条件表达式，如果已经有条件表达式，则用 AND 将它们连接起来</param>
        public SqlUpdate AddCondition(SqlCondition condition)
        {
            conditionBuilder.AddCondition(condition);
            return this;
        }

        /// <summary>
        /// 添加一个条件
        /// </summary>
        /// <param name="sql">条件表达式的左边</param>
        /// <param name="sql2">条件表达式的右边</param>
        public SqlUpdate AddCondition(string sql, string sql2)
        {
            conditionBuilder.AddCondition(sql + sql2);
            return this;
        }

        /// <summary>
        /// 添加一个条件
        /// </summary>
        /// <param name="logicOperator">条件运算符，必需是 AND/OR, 需要自行处理换行</param>
        /// <param name="condition">条件</param>
        public SqlUpdate AddCondition(string logicOperator, SqlCondition condition)
        {
            conditionBuilder.AddCondition(logicOperator, condition);
            return this;
        }

        /// <summary>
        /// 获取条件部分
        /// </summary>
        /// <returns>条件表达式</returns>
        public string GetCondition()
        {
            return conditionBuilder.ToString();
        }

        /// <summary>
        /// 添加一个版本号列
        /// </summary>
        /// <returns></returns>
        public bool AddVersionColumn()
        {
            if (string.IsNullOrEmpty(versionInfo.VersionColumn))
            {
                return false;
            }

            versionInfo.HasVersionInfo = true;
            AddVersionColumnInternal();
            return true;
        }

        /// <summary>
        /// 添加一个版本号列
        /// </summary>
        /// <param name="versionColumn"></param>
        /// <param name="currentVersion"></param>
        /// <param name="versonDateTimeColumn"></param>
        /// <returns></returns>
        public bool AddVersionColumn(string versionColumn, int currentVersion, string versonDateTimeColumn)
        {
            versionInfo.VersionColumn = versionColumn;
            versionInfo.VersionDateColumn = versonDateTimeColumn;
            versionInfo.CurrentVersion = currentVersion;
            AddVersionColumnInternal();
            return true;
        }
        #endregion

        #region 输出
        /// <summary>
        /// 输出SQL语句
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sql = new StringBuilder();

            sql.Append("UPDATE ");
            NL(sql.Append(tableName));
            NL(sql.Append(" SET"));

            sql.Append(sbSql.ToString());
            if (Condition.Length > 0)
            {
                NL(sql.Append(CompactMode ? " WHERE":"WHERE"));
                sql.Append(Condition.ToString());
                if (versionInfo.HasVersionInfo)
                {
                    sql.Append((CompactMode ? " AND " : "    AND ")
                        + versionInfo.VersionColumn
                        + " = "
                        + versionInfo.CurrentVersion.ToString());
                }
            }
            else
            {
                if (versionInfo.HasVersionInfo)
                {
                    sql.Append((CompactMode ? " " : "    ")
                        + versionInfo.VersionColumn
                        + " = "
                        + versionInfo.CurrentVersion.ToString());
                }
            }

            return sql.ToString();
        }
        #endregion
    }
}
